Merge "Add LoadBalancer::setTransactionListener()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 1 Sep 2016 06:42:46 +0000 (06:42 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 1 Sep 2016 06:42:46 +0000 (06:42 +0000)
RELEASE-NOTES-1.28
includes/Linker.php
includes/api/ApiParamInfo.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/deferred/SqlDataUpdate.php
includes/filerepo/file/LocalFile.php
tests/phpunit/includes/LinkerTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.php

index 6639a95..600ac70 100644 (file)
@@ -96,6 +96,8 @@ production.
   ApiPageSet may contain entries where the 'from' value is percent-encoded as
   the raw value cannot be represented in a valid API response. These are
   indicated by a 'fromencoded' boolean alongside the existing 'from' parameter.
+* (T28680) action=paraminfo can now return info about all submodules of a
+  module without listing them all explicitly.
 
 === Action API internal changes in 1.28 ===
 * Added a new hook, 'ApiMakeParserOptions', to allow extensions to better
index 2b38a96..f8b1bae 100644 (file)
@@ -993,9 +993,10 @@ class Linker {
                        $page = Title::makeTitle( NS_USER, $userName );
                }
 
+               // Wrap the output with <bdi> tags for directionality isolation
                return self::link(
                        $page,
-                       htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
+                       '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>',
                        [ 'class' => $classes ]
                );
        }
index 25e1a7f..caf0cd7 100644 (file)
@@ -46,7 +46,39 @@ class ApiParamInfo extends ApiBase {
                $this->context->setLanguage( $this->getMain()->getLanguage() );
 
                if ( is_array( $params['modules'] ) ) {
-                       $modules = $params['modules'];
+                       $modules = [];
+                       foreach ( $params['modules'] as $path ) {
+                               if ( $path === '*' || $path === '**' ) {
+                                       $path = "main+$path";
+                               }
+                               if ( substr( $path, -2 ) === '+*' || substr( $path, -2 ) === ' *' ) {
+                                       $submodules = true;
+                                       $path = substr( $path, 0, -2 );
+                                       $recursive = false;
+                               } elseif ( substr( $path, -3 ) === '+**' || substr( $path, -3 ) === ' **' ) {
+                                       $submodules = true;
+                                       $path = substr( $path, 0, -3 );
+                                       $recursive = true;
+                               } else {
+                                       $submodules = false;
+                               }
+
+                               if ( $submodules ) {
+                                       try {
+                                               $module = $this->getModuleFromPath( $path );
+                                       } catch ( UsageException $ex ) {
+                                               $this->setWarning( $ex->getMessage() );
+                                       }
+                                       $submodules = $this->listAllSubmodules( $module, $recursive );
+                                       if ( $submodules ) {
+                                               $modules = array_merge( $modules, $submodules );
+                                       } else {
+                                               $this->setWarning( "Module $path has no submodules" );
+                                       }
+                               } else {
+                                       $modules[] = $path;
+                               }
+                       }
                } else {
                        $modules = [];
                }
@@ -69,6 +101,8 @@ class ApiParamInfo extends ApiBase {
                        $formatModules = [];
                }
 
+               $modules = array_unique( $modules );
+
                $res = [];
 
                foreach ( $modules as $m ) {
@@ -121,6 +155,29 @@ class ApiParamInfo extends ApiBase {
                $result->addValue( null, $this->getModuleName(), $res );
        }
 
+       /**
+        * List all submodules of a module
+        * @param ApiBase $module
+        * @param boolean $recursive
+        * @return string[]
+        */
+       private function listAllSubmodules( ApiBase $module, $recursive ) {
+               $manager = $module->getModuleManager();
+               if ( $manager ) {
+                       $paths = [];
+                       $names = $manager->getNames();
+                       sort( $names );
+                       foreach ( $names as $name ) {
+                               $submodule = $manager->getModule( $name );
+                               $paths[] = $submodule->getModulePath();
+                               if ( $recursive && $submodule->getModuleManager() ) {
+                                       $paths = array_merge( $paths, $this->listAllSubmodules( $submodule, $recursive ) );
+                               }
+                       }
+               }
+               return $paths;
+       }
+
        /**
         * @param array $res Result array
         * @param string $key Result key
@@ -449,8 +506,10 @@ class ApiParamInfo extends ApiBase {
 
        protected function getExamplesMessages() {
                return [
-                       'action=paraminfo&modules=parse|phpfm|query+allpages|query+siteinfo'
+                       'action=paraminfo&modules=parse|phpfm|query%2Ballpages|query%2Bsiteinfo'
                                => 'apihelp-paraminfo-example-1',
+                       'action=paraminfo&modules=query%2B*'
+                               => 'apihelp-paraminfo-example-2',
                ];
        }
 
index a68a87f..974e0aa 100644 (file)
        "apihelp-options-example-complex": "Reset all preferences, then set <kbd>skin</kbd> and <kbd>nickname</kbd>.",
 
        "apihelp-paraminfo-description": "Obtain information about API modules.",
-       "apihelp-paraminfo-param-modules": "List of module names (values of the <var>action</var> and <var>format</var> parameters, or <kbd>main</kbd>). Can specify submodules with a <kbd>+</kbd>.",
+       "apihelp-paraminfo-param-modules": "List of module names (values of the <var>action</var> and <var>format</var> parameters, or <kbd>main</kbd>). Can specify submodules with a <kbd>+</kbd>, or all submodules with <kbd>+*</kbd>, or all submodules recursively with <kbd>+**</kbd>.",
        "apihelp-paraminfo-param-helpformat": "Format of help strings.",
        "apihelp-paraminfo-param-querymodules": "List of query module names (value of <var>prop</var>, <var>meta</var> or <var>list</var> parameter). Use <kbd>$1modules=query+foo</kbd> instead of <kbd>$1querymodules=foo</kbd>.",
        "apihelp-paraminfo-param-mainmodule": "Get information about the main (top-level) module as well. Use <kbd>$1modules=main</kbd> instead.",
        "apihelp-paraminfo-param-pagesetmodule": "Get information about the pageset module (providing titles= and friends) as well.",
        "apihelp-paraminfo-param-formatmodules": "List of format module names (value of <var>format</var> parameter). Use <var>$1modules</var> instead.",
        "apihelp-paraminfo-example-1": "Show info for <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, and <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+       "apihelp-paraminfo-example-2": "Show info for all submodules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd>.",
 
        "apihelp-parse-description": "Parses content and returns parser output.\n\nSee the various prop-modules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd> to get information from the current version of a page.\n\nThere are several ways to specify the text to parse:\n# Specify a page or revision, using <var>$1page</var>, <var>$1pageid</var>, or <var>$1oldid</var>.\n# Specify content explicitly, using <var>$1text</var>, <var>$1title</var>, and <var>$1contentmodel</var>.\n# Specify only a summary to parse. <var>$1prop</var> should be given an empty value.",
        "apihelp-parse-param-title": "Title of page the text belongs to. If omitted, <var>$1contentmodel</var> must be specified, and [[API]] will be used as the title.",
index 650acb9..abbc69b 100644 (file)
        "apihelp-paraminfo-param-pagesetmodule": "{{doc-apihelp-param|paraminfo|pagesetmodule}}",
        "apihelp-paraminfo-param-formatmodules": "{{doc-apihelp-param|paraminfo|formatmodules}}",
        "apihelp-paraminfo-example-1": "{{doc-apihelp-example|paraminfo}}",
+       "apihelp-paraminfo-example-2": "{{doc-apihelp-example|paraminfo}}",
        "apihelp-parse-description": "{{doc-apihelp-description|parse}}",
        "apihelp-parse-param-title": "{{doc-apihelp-param|parse|title}}",
        "apihelp-parse-param-text": "{{doc-apihelp-param|parse|text}}",
index ff06915..c7163ea 100644 (file)
@@ -29,6 +29,8 @@
  *       a transaction will automatically be wrapped around the update. Starting another
  *       one would break the outer transaction bracket. If need be, subclasses can override
  *       the beginTransaction() and commitTransaction() methods.
+ *
+ * @deprecated Since 1.28 Use DataUpdate directly, injecting the database
  */
 abstract class SqlDataUpdate extends DataUpdate {
        /** @var IDatabase Database connection reference */
index 8d25726..de3cdbe 100644 (file)
 
 use \MediaWiki\Logger\LoggerFactory;
 
-/**
- * Bump this number when serialized cache records may be incompatible.
- */
-define( 'MW_FILE_VERSION', 9 );
-
 /**
  * Class to represent a local file in the wiki's own database
  *
@@ -46,6 +41,8 @@ define( 'MW_FILE_VERSION', 9 );
  * @ingroup FileAbstraction
  */
 class LocalFile extends File {
+       const VERSION = 10; // cache version
+
        const CACHE_FIELD_MAX_LEN = 1000;
 
        /** @var bool Does the file exist on disk? (loadFromXxx) */
@@ -240,83 +237,71 @@ class LocalFile extends File {
         * @return string|bool
         */
        function getCacheKey() {
-               $hashedName = md5( $this->getName() );
-
-               return $this->repo->getSharedCacheKey( 'file', $hashedName );
+               return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
        }
 
        /**
-        * Try to load file metadata from memcached. Returns true on success.
-        * @return bool
+        * Try to load file metadata from memcached, falling back to the database
         */
        private function loadFromCache() {
                $this->dataLoaded = false;
                $this->extraDataLoaded = false;
-               $key = $this->getCacheKey();
 
+               $key = $this->getCacheKey();
                if ( !$key ) {
-                       return false;
-               }
-
-               $cache = ObjectCache::getMainWANInstance();
-               $cachedValues = $cache->get( $key );
+                       $this->loadFromDB( self::READ_NORMAL );
 
-               // Check if the key existed and belongs to this version of MediaWiki
-               if ( is_array( $cachedValues ) && $cachedValues['version'] == MW_FILE_VERSION ) {
-                       $this->fileExists = $cachedValues['fileExists'];
-                       if ( $this->fileExists ) {
-                               $this->setProps( $cachedValues );
-                       }
-                       $this->dataLoaded = true;
-                       $this->extraDataLoaded = true;
-                       foreach ( $this->getLazyCacheFields( '' ) as $field ) {
-                               $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
-                       }
+                       return;
                }
 
-               return $this->dataLoaded;
-       }
-
-       /**
-        * Save the file metadata to memcached
-        * @param array $cacheSetOpts Result of Database::getCacheSetOptions()
-        */
-       private function saveToCache( array $cacheSetOpts ) {
-               $this->load();
+               $cache = ObjectCache::getMainWANInstance();
+               $cachedValues = $cache->getWithSetCallback(
+                       $key,
+                       $cache::TTL_WEEK,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
+                               $setOpts += Database::getCacheSetOptions( $this->repo->getSlaveDB() );
+
+                               $this->loadFromDB( self::READ_NORMAL );
+
+                               $fields = $this->getCacheFields( '' );
+                               $cacheVal['fileExists'] = $this->fileExists;
+                               if ( $this->fileExists ) {
+                                       foreach ( $fields as $field ) {
+                                               $cacheVal[$field] = $this->$field;
+                                       }
+                               }
+                               // Strip off excessive entries from the subset of fields that can become large.
+                               // If the cache value gets to large it will not fit in memcached and nothing will
+                               // get cached at all, causing master queries for any file access.
+                               foreach ( $this->getLazyCacheFields( '' ) as $field ) {
+                                       if ( isset( $cacheVal[$field] )
+                                               && strlen( $cacheVal[$field] ) > 100 * 1024
+                                       ) {
+                                               unset( $cacheVal[$field] ); // don't let the value get too big
+                                       }
+                               }
 
-               $key = $this->getCacheKey();
-               if ( !$key ) {
-                       return;
-               }
+                               if ( $this->fileExists ) {
+                                       $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
+                               } else {
+                                       $ttl = $cache::TTL_DAY;
+                               }
 
-               $fields = $this->getCacheFields( '' );
-               $cacheVal = [ 'version' => MW_FILE_VERSION ];
-               $cacheVal['fileExists'] = $this->fileExists;
+                               return $cacheVal;
+                       },
+                       [ 'version' => self::VERSION ]
+               );
 
+               $this->fileExists = $cachedValues['fileExists'];
                if ( $this->fileExists ) {
-                       foreach ( $fields as $field ) {
-                               $cacheVal[$field] = $this->$field;
-                       }
+                       $this->setProps( $cachedValues );
                }
 
-               // Strip off excessive entries from the subset of fields that can become large.
-               // If the cache value gets to large it will not fit in memcached and nothing will
-               // get cached at all, causing master queries for any file access.
+               $this->dataLoaded = true;
+               $this->extraDataLoaded = true;
                foreach ( $this->getLazyCacheFields( '' ) as $field ) {
-                       if ( isset( $cacheVal[$field] ) && strlen( $cacheVal[$field] ) > 100 * 1024 ) {
-                               unset( $cacheVal[$field] ); // don't let the value get too big
-                       }
+                       $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
                }
-
-               // Cache presence for 1 week and negatives for 1 day
-               $wanCache = ObjectCache::getMainWANInstance();
-               if ( $this->fileExists ) {
-                       $ttl = $wanCache::TTL_WEEK;
-                       $ttl = $wanCache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
-               } else {
-                       $ttl = $wanCache::TTL_DAY;
-               }
-               $wanCache->set( $key, $cacheVal, $ttl, $cacheSetOpts );
        }
 
        /**
@@ -551,13 +536,13 @@ class LocalFile extends File {
         */
        function load( $flags = 0 ) {
                if ( !$this->dataLoaded ) {
-                       if ( ( $flags & self::READ_LATEST ) || !$this->loadFromCache() ) {
-                               $opts = Database::getCacheSetOptions( $this->repo->getSlaveDB() );
+                       if ( $flags & self::READ_LATEST ) {
                                $this->loadFromDB( $flags );
-                               $this->saveToCache( $opts );
+                       } else {
+                               $this->loadFromCache();
                        }
-                       $this->dataLoaded = true;
                }
+
                if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
                        // @note: loads on name/timestamp to reduce race condition problems
                        $this->loadExtraFromDB();
@@ -773,7 +758,7 @@ class LocalFile extends File {
 
                if ( $type == 'text' ) {
                        return $this->user_text;
-               } elseif ( $type == 'id' ) {
+               } else { // id
                        return (int)$this->user;
                }
        }
@@ -1628,7 +1613,9 @@ class LocalFile extends File {
                        $sha1 = $repo->isVirtualUrl( $srcPath )
                                ? $repo->getFileSha1( $srcPath )
                                : FSFile::getSha1Base36FromPath( $srcPath );
-                       $dst = $repo->getBackend()->getPathForSHA1( $sha1 );
+                       /** @var FileBackendDBRepoWrapper $wrapperBackend */
+                       $wrapperBackend = $repo->getBackend();
+                       $dst = $wrapperBackend->getPathForSHA1( $sha1 );
                        $status = $repo->quickImport( $src, $dst );
                        if ( $flags & File::DELETE_SOURCE ) {
                                unlink( $srcPath );
@@ -2499,6 +2486,7 @@ class LocalFileRestoreBatch {
         * @return FileRepoStatus
         */
        public function execute() {
+               /** @var Language */
                global $wgLang;
 
                $repo = $this->file->getRepo();
index 63753f9..3edf99f 100644 (file)
@@ -37,34 +37,34 @@ class LinkerTest extends MediaWikiLangTestCase {
                        [
                                '<a href="/wiki/Special:Contributions/JohnDoe" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/JohnDoe">JohnDoe</a>',
+                                       . 'title="Special:Contributions/JohnDoe"><bdi>JohnDoe</bdi></a>',
                                0, 'JohnDoe', false,
                        ],
                        [
                                '<a href="/wiki/Special:Contributions/::1" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/::1">::1</a>',
+                                       . 'title="Special:Contributions/::1"><bdi>::1</bdi></a>',
                                0, '::1', false,
                                'Anonymous with pretty IPv6'
                        ],
                        [
                                '<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/0:0:0:0:0:0:0:1">::1</a>',
+                                       . 'title="Special:Contributions/0:0:0:0:0:0:0:1"><bdi>::1</bdi></a>',
                                0, '0:0:0:0:0:0:0:1', false,
                                'Anonymous with almost pretty IPv6'
                        ],
                        [
                                '<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001">::1</a>',
+                                       . 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001"><bdi>::1</bdi></a>',
                                0, '0000:0000:0000:0000:0000:0000:0000:0001', false,
                                'Anonymous with full IPv6'
                        ],
                        [
                                '<a href="/wiki/Special:Contributions/::1" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/::1">AlternativeUsername</a>',
+                                       . 'title="Special:Contributions/::1"><bdi>AlternativeUsername</bdi></a>',
                                0, '::1', 'AlternativeUsername',
                                'Anonymous with pretty IPv6 and an alternative username'
                        ],
@@ -73,14 +73,14 @@ class LinkerTest extends MediaWikiLangTestCase {
                        [
                                '<a href="/wiki/Special:Contributions/127.0.0.1" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/127.0.0.1">127.0.0.1</a>',
+                                       . 'title="Special:Contributions/127.0.0.1"><bdi>127.0.0.1</bdi></a>',
                                0, '127.0.0.1', false,
                                'Anonymous with IPv4'
                        ],
                        [
                                '<a href="/wiki/Special:Contributions/127.0.0.1" '
                                        . 'class="mw-userlink mw-anonuserlink" '
-                                       . 'title="Special:Contributions/127.0.0.1">AlternativeUsername</a>',
+                                       . 'title="Special:Contributions/127.0.0.1"><bdi>AlternativeUsername</bdi></a>',
                                0, '127.0.0.1', 'AlternativeUsername',
                                'Anonymous with IPv4 and an alternative username'
                        ],
index b09e5b1..c289839 100644 (file)
@@ -50,7 +50,7 @@ abstract class LogFormatterTestCase extends MediaWikiLangTestCase {
        private static function removeSomeHtml( $html ) {
                $html = str_replace( '&quot;', '"', $html );
                $html = preg_replace( '/\xE2\x80[\x8E\x8F]/', '', $html ); // Strip lrm/rlm
-               return trim( preg_replace( '/<(a|span)[^>]*>([^<]*)<\/\1>/', '$2', $html ) );
+               return trim( strip_tags( $html ) );
        }
 
        private static function removeApiMetaData( $val ) {